/*
 * Class representing a subscriber of a library
 * A subscriber knows his first and last name,
 * his born date and his number in the library
 * plus it knows the number of dvds and books
 * that it currently loans
 * @author M.T. Segarra, M.A Taupenot, G. Piraux
 * @version 2.0.1
 */
package subscribersManagement;


import java.io.Serializable;
import java.util.*;

import loansManagement.Loan;
import main.Constraints;
import exceptions.*;

import javax.persistence.*;

import booksManagement.Copy;
import booksManagement.Media;
import subscribersManagement.Entitled;
import subscribersManagement.UtilsDate;

/**
 * Entity implementation class for Entity: Subscriber
 */
@Entity
@Inheritance (strategy=InheritanceType.JOINED)
@Table(name="subscribers")
@NamedQuery(name="findAllSubscribers", query="select s from Subscriber s")
public class Subscriber implements Serializable{

	private static final long serialVersionUID = 1L;
	
	/**
   * @uml.property  name="subscriptionDate"
   */
	private Calendar subscriptionDate;
	
	/**
   * Getter of the property <tt>subscriptionDate</tt>
   * @return   Returns the subscriptionDate
   * @uml.property  name="subscriptionDate"
   */
	public Calendar getSubscriptionDate() {
  	return subscriptionDate;
  }
	
	@Transient
	//@ManyToMany(mappedBy="reservers")
	private List<Media> reservations;
	
	/*@Transient
	private List<Copy> reservedCopies;*/

	/**
   * Setter of the property <tt>subscriptionDate</tt>
   * @param newDate
   * @throws NullPointerException
   * @uml.property  name="subscriptionDate"
   */
	public void setSubscriptionDate(Calendar newDate) throws NullPointerException {
		if (newDate==null)
			throw new NullPointerException("Empty Calendar type pointer");
		else
			this.subscriptionDate = newDate;
  }

	/**
   * @uml.property  name="firstName"
   */
	private String firstName;

	/**
   * Getter of the property <tt>firstName</tt>
   * @return       Returns the firstName.
   * @uml.property  name="firstName"
   */
	public String getFirstName() {
		return firstName;
	}

	/**
   * Setter of the property <tt>firstName</tt>
   * @param firstName       The firstName to set.
   * @throws NullPointerException
   * @uml.property  name="firstName"
   */
	public void setFirstName(String firstName) throws NullPointerException {
		if (firstName==null) 
			throw new NullPointerException("Empty String type pointer");
		else
			this.firstName = firstName;
	}

	/**
   * @uml.property  name="lastName"
   */
	private String lastName;

	/**
   * Getter of the property <tt>lasName</tt>
   * @return       Returns the lastName.
   * @uml.property  name="lastName"
   */
	public String getLastName() {
		return lastName;
	}

	/**
   * Setter of the property <tt>lastName</tt>
   * @param lastname       The lastName to set.
   * @throws NullPointerException
   * @uml.property  name="lastName"
   */
	public void setLastName(String lastName) throws NullPointerException {
		if (lastName==null)
			throw new NullPointerException("Empty String type pointer");
		else
			this.lastName = lastName;
	}

	/**
   * @uml.property  name="bornDate"
   */
	private Calendar bornDate;

	/**
   * Getter of the property <tt>bornDate</tt>
   * @return       Returns the bornDate.
   * @uml.property  name="bornDate"
   */
	public Calendar getBornDate() {
		return bornDate;
	}

	/**
   * Setter of the property <tt>bornDate</tt>
   * @param bornDate       The bornDate to set.
   * @throws NullPointerException
   * @throws BadParametersException
   * @uml.property  name="bornDate"
   */
	public void setBornDate(Calendar bornDate) throws NullPointerException, BadParametersException {
		if (bornDate==null)
			throw new NullPointerException("null value of birth date");
		if ((bornDate.after(Calendar.getInstance())))
			throw new BadParametersException("the value of the birth date is later than the current time");
		else
			this.bornDate = bornDate;
	}

	/**
   * @uml.property  name="currentLoans"
   */
	//@Transient
	@OneToMany(mappedBy = "lender")
	private List<Loan> currentLoans;

	/**
   * Getter of the property <tt>currentLoans</tt>
   * @return       Returns the currentLoans list.
   * @uml.property  name="currentLoans"
   */
	public List<Loan> getCurrentLoans() {
		return currentLoans;
	}
	
	/**
   * @uml.property  name="booksLoaned"
   */
	private int booksLoanedNb;
	
	/**
   * Getter of the property <tt>booksLoanedNb</tt>
   * @return   Returns the number of books currently loans
   * @uml.property  name="booksLoanedNb"
   */
	public int getBooksLoanedNb() {
		return booksLoanedNb;
	}
	
	/**
   * Setter of the property <tt>booksLoanedNb</tt>
   * @param nb
   * @throws BadParametersException
   * @uml.property  name="booksLoanedNb"
   */
	public void setBooksLoanedNb(int nb) throws BadParametersException {
	if (nb < 0)
		throw new BadParametersException("Number of books loaned can not be negative.");
	else
		booksLoanedNb=nb;
	}
	
	/**
   * @uml.property  name="dvdLoanedNb"
   */
	private int dvdLoanedNb;
	
	/**
   * Getter of the property <tt>dvdLoanedNb</tt>
   * @return   the number of DVDs currently loaned
   * @uml.property  name="dvdLoanedNb"
   */
	public int getDvdLoanedNb() {
		return dvdLoanedNb;
	}
	
	/**
   * Setter of the property <tt>dvdLoanedNb</tt>
   * @param nb
   * @throws BadParametersException
   * @uml.property  name="dvdLoanedNb"
   */
	public void setDvdLoanedNb(int nb) throws BadParametersException {
	if (nb < 0)
		throw new BadParametersException("Number of DVDs loaned can not be negative.");
	else
		dvdLoanedNb=nb;
	}
	
	/**
	 * Basic constructor with no arguments
	 */
	public Subscriber(){
	}	
	
	/**
	 * Constructor of a subscriber Generates the number of the subscriber
	 * 
	 * @params firstName first name of the subscriber
	 * @params lastName last name of the subscriber
	 * @params bornDate born date of the subscriber
	 * @throws BadParametersException
	 */
	public Subscriber(String firstName, String lastName, Calendar bornDate)
			throws BadParametersException {
			this.setFirstName(firstName);
			this.setLastName(lastName);
			this.setBornDate(bornDate);
			this.subscriptionDate = new GregorianCalendar();
			this.dvdLoanedNb = 0;
			this.booksLoanedNb = 0;
			this.currentLoans = new ArrayList<Loan>();
			this.entitles = new ArrayList<Entitled>();
			
	}

	/**
	 * Decides if the subscriber object (parameter) is the same subscriber as
	 * this one Same first name, last name, and born date
	 * 
	 * @param subscriber object to be compared with this one
	 * 
	 * @return true if parameter object and this one are equal
	 * 
	 * @return false if parameter object is null or different from this one
	 * @throws NullPointerException
	 */
	@Override
	public boolean equals(Object subscriber) throws NullPointerException {
		if (subscriber == null)
			throw new NullPointerException("Empty subscriber type pointer.");
		else{
			Subscriber s = (Subscriber) subscriber;

			if (s.number == number)
				return true;

			if ((s.firstName == null) || (s.lastName == null)
					|| (s.bornDate == null))
				throw new NullPointerException("Either the suppose-to-be suscriber firstName, lastName or bornDate is null.");

			boolean res = (s.firstName.equals(firstName))
					&& (s.lastName.equals(lastName))
					&& (s.bornDate.equals(bornDate));

			return res;
		}
	}

	/**
   * @uml.property  name="number"
   */
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long number;

	/**
   * @param  number
   * @uml.property  name="number"
   */
	public void setNumber(long number)
  {
  	this.number = number;
  }

	/**
   * Getter of the property <tt>number</tt>
   * @return       Returns the number.
   * @uml.property  name="number"
   */
	public long getNumber() {
		return number;
	}

	/**
	 * @return true if the subscriber has loans
	 */
	public boolean existingLoans() {
		return currentLoans.size() > 0;
	}

	/**
	 * Add a new loan to the subscriber if quotas are good
	 * 
	 * @param loan
	 *            the loan to add
	 * @throws TooManyLoansException
	 * 						if one of the quotas is reached
	 * @throws NullPointerException
	 */
	public void lend(Loan loan) throws TooManyLoansException,
			NullPointerException {
		if (loan == null)
			throw new NullPointerException("Empty loan type pointer.");
		if (loan.isABook()){
			if (!canLendBooks())
				throw new TooManyLoansException("Subscriber can not lend more books.");
			else {
				this.booksLoanedNb++;
				currentLoans.add(loan);
			}
		}
		if (loan.isADVD()){
			if (!canLendDVD())
				throw new TooManyLoansException("Subscriber can not lend more DVDs.");
			else {
				this.dvdLoanedNb++;
				currentLoans.add(loan);
			}
		}
	}
	private int getAge()
	{
	  Calendar curr = Calendar.getInstance();
	  int yeardiff = curr.get(Calendar.YEAR) - bornDate.get(Calendar.YEAR);
	  curr.add(Calendar.YEAR,-yeardiff);
	  if(bornDate.after(curr))
	  {
	    yeardiff = yeardiff - 1;
	  }
	  return yeardiff;
	}

	/**
	 * @return true if the subscriber can lend books
	 * @return false if not
	 */
	public boolean canLendBooks(){
		int time; //time since subscription (in days)
		Date today = new Date(); //today's date and time
		Date subscription = this.subscriptionDate.getTime(); //Date corresponding to the GregorianCalendar subscriptionDate
		time = subscribersManagement.UtilsDate.diff(today, subscription);
		
		if (this.getAge() < Constraints.AGE_LIMIT){
			return booksLoanedNb < Constraints.MAX_BOOKSLOANS_CHILD;
		}else{
			if (time < 0)
				return false;//impossible treatment, will be detected chile testing and change if needed
			if (time < 366)
				return booksLoanedNb < Constraints.MAX_BOOKSLOANS;
			if (time >= 366 && time < 731)
				return booksLoanedNb < Constraints.MAX_BOOKSLOANS_2A;
			else
				return booksLoanedNb < Constraints.MAX_BOOKSLOANS_3A;
		}
		
	}
	
	/**
	 * @return true if the subscriber can lend dvd
	 * @return false if not
	 */
	public boolean canLendDVD(){
		int time; //time since subscription (in days)
		Date today = new Date(); //today's date and time
		Date subscription = this.subscriptionDate.getTime(); //Date corresponding to the GregorianCalendar subscriptionDate
		
		time = subscribersManagement.UtilsDate.diff(today, subscription);
		
		if (this.getAge() < Constraints.AGE_LIMIT){
			return dvdLoanedNb < Constraints.MAX_DVDLOANS_CHILD;
		}else{
				if (time < 0)
					return false;//impossible treatment, will be detected chile testing and change if needed
				if (time < 366)
					return dvdLoanedNb < Constraints.MAX_DVDLOANS;
				if (time >= 366 && time < 731)
					return dvdLoanedNb < Constraints.MAX_DVDLOANS_2A;
				else
					return dvdLoanedNb < Constraints.MAX_DVDLOANS_3A;
			}
		/*
		public boolean canReserveDVD(){
			if 
			return
		}
		public boolean canReserveBook(){
			
		}*/
		
	}	

	/**
	 * Remove the loan in parameter from the list of current loans of the
	 * subscriber
	 * 
	 * @param loan
	 *            the loan to be ended
	 * @throws BadParametersException
	 * @throws LentBookException
	 *             if loan not found in the list
	 */
	public void returnCopy(Loan loan) throws NullPointerException,
			LentBookException {
		if (loan == null)
			throw new NullPointerException("Empty loan type pointer.");

		if (!currentLoans.remove(loan))
			throw new LentBookException("The lender does not contain the loan");
		
		else {
			if (loan.isABook()){
					this.booksLoanedNb--;
				}
			if (loan.isADVD()){
					this.dvdLoanedNb--;
				}
		}
		
	}

	/**
   * Setter of the property <tt>currentLoans</tt>
   * @param currentLoans       The currentLoans to set.
   * @throws NullPointerException
   * @uml.property  name="currentLoans"
   */
	public void setCurrentLoans(List<Loan> currentLoans) throws NullPointerException {
		if (currentLoans == null)
			throw new NullPointerException();
		else
			this.currentLoans = currentLoans;
	}

	/**
	 * Returns a description of the subscriber
	 */
	@Override
	public String toString() {
		return this.getNumber() + " " + this.firstName + ", " + this.lastName;
	}

	/**
   * @uml.property  name="entitles"
   */
	@OneToMany(mappedBy = "mainSubscriber")
	private List<Entitled> entitles;

	/**
   * Getter of the property <tt>entitles</tt>
   * @return        Returns the entitleds.
   * @uml.property  name="entitles"
   */
	public List<Entitled> getEntitles() {
		return this.entitles;
	}

	/**
   * Setter of the property <tt>entitles</tt>
   * @param entitles        The entitleds to set.
   * @uml.property  name="entitles"
   */
	public void setEntitles(List<Entitled> entitles) {
		this.entitles = entitles;
	}
	
	public void addEntitled(Entitled entitled) {
		this.entitles.add(entitled);
	}
	public void removeEntitled(Entitled entitled) {
		this.entitles.remove(entitled);
	}

	/*public List<Copy> getReservedCopies()
  {
	  return reservedCopies;
  }

	public void setReservedCopies(List<Copy> reservedCopies)
  {
	  this.reservedCopies = reservedCopies;
  }*/
	
}
